﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;

namespace UniversityCrawler
{
    public class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
    }

    public class MainForm : Form
    {
        private TextBox txtKeyword;
        private Button btnSearch;
        private Label lblStatus;
        private ListBox lstUrls;
        private DataGridView gridPhones;
        private Label lblUrlsCount;
        private Label lblPhonesCount;
        private TrackBar trackMaxThreads;
        private Label lblMaxThreads;
        private Label lblMaxThreadsValue;
        private Button btnStop;

        private CrawlerEngine crawler;
        private CancellationTokenSource cts;

        public MainForm()
        {
            InitializeComponents();
            crawler = new CrawlerEngine();
        }

        private void InitializeComponents()
        {
            this.Text = "高校电话号码爬虫";
            this.Size = new Size(800, 600);
            this.StartPosition = FormStartPosition.CenterScreen;

            // 关键词输入
            Label lblKeyword = new Label
            {
                Text = "搜索关键词:",
                Location = new Point(10, 15),
                AutoSize = true
            };
            this.Controls.Add(lblKeyword);

            txtKeyword = new TextBox
            {
                Location = new Point(100, 12),
                Size = new Size(300, 23),
                Text = "大学 联系电话"
            };
            this.Controls.Add(txtKeyword);

            // 最大线程数控制
            Label lblThreads = new Label
            {
                Text = "最大线程数:",
                Location = new Point(410, 15),
                AutoSize = true
            };
            this.Controls.Add(lblThreads);

            trackMaxThreads = new TrackBar
            {
                Location = new Point(490, 10),
                Size = new Size(150, 45),
                Minimum = 1,
                Maximum = 20,
                Value = 5,
                TickFrequency = 1
            };
            trackMaxThreads.ValueChanged += (s, e) =>
            {
                lblMaxThreadsValue.Text = trackMaxThreads.Value.ToString();
            };
            this.Controls.Add(trackMaxThreads);

            lblMaxThreadsValue = new Label
            {
                Text = "5",
                Location = new Point(650, 15),
                AutoSize = true
            };
            this.Controls.Add(lblMaxThreadsValue);

            // 搜索按钮
            btnSearch = new Button
            {
                Text = "开始搜索",
                Location = new Point(10, 45),
                Size = new Size(90, 30)
            };
            btnSearch.Click += BtnSearch_Click;
            this.Controls.Add(btnSearch);

            // 停止按钮
            btnStop = new Button
            {
                Text = "停止",
                Location = new Point(110, 45),
                Size = new Size(90, 30),
                Enabled = false
            };
            btnStop.Click += BtnStop_Click;
            this.Controls.Add(btnStop);

            // 状态标签
            lblStatus = new Label
            {
                Text = "准备就绪",
                Location = new Point(210, 53),
                AutoSize = true
            };
            this.Controls.Add(lblStatus);

            // 已爬取URL计数
            lblUrlsCount = new Label
            {
                Text = "已爬取URL: 0",
                Location = new Point(10, 85),
                AutoSize = true
            };
            this.Controls.Add(lblUrlsCount);

            // 已找到电话计数
            lblPhonesCount = new Label
            {
                Text = "已找到电话: 0",
                Location = new Point(150, 85),
                AutoSize = true
            };
            this.Controls.Add(lblPhonesCount);

            // URL列表
            Label lblUrls = new Label
            {
                Text = "已爬取URL:",
                Location = new Point(10, 110),
                AutoSize = true
            };
            this.Controls.Add(lblUrls);

            lstUrls = new ListBox
            {
                Location = new Point(10, 130),
                Size = new Size(380, 420),
                ScrollAlwaysVisible = true
            };
            this.Controls.Add(lstUrls);

            // 电话号码表格
            Label lblPhones = new Label
            {
                Text = "找到的电话号码:",
                Location = new Point(400, 110),
                AutoSize = true
            };
            this.Controls.Add(lblPhones);

            gridPhones = new DataGridView
            {
                Location = new Point(400, 130),
                Size = new Size(380, 420),
                AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill,
                AllowUserToAddRows = false,
                ReadOnly = true
            };
            gridPhones.Columns.Add("PhoneNumber", "电话号码");
            gridPhones.Columns.Add("UrlCount", "URL数量");
            gridPhones.CellClick += GridPhones_CellClick;
            this.Controls.Add(gridPhones);
        }

        private async void BtnSearch_Click(object sender, EventArgs e)
        {
            string keyword = txtKeyword.Text.Trim();
            if (string.IsNullOrEmpty(keyword))
            {
                MessageBox.Show("请输入搜索关键词");
                return;
            }

            btnSearch.Enabled = false;
            btnStop.Enabled = true;
            lstUrls.Items.Clear();
            gridPhones.Rows.Clear();
            lblStatus.Text = "正在搜索...";
            lblUrlsCount.Text = "已爬取URL: 0";
            lblPhonesCount.Text = "已找到电话: 0";

            cts = new CancellationTokenSource();

            try
            {
                crawler.MaxConcurrency = trackMaxThreads.Value;
                crawler.ProgressCallback = UpdateProgress;

                await crawler.StartCrawlingAsync(keyword, cts.Token);

                lblStatus.Text = "搜索完成";
            }
            catch (OperationCanceledException)
            {
                lblStatus.Text = "搜索已取消";
            }
            catch (Exception ex)
            {
                lblStatus.Text = "发生错误";
                MessageBox.Show($"搜索时发生错误: {ex.Message}");
            }
            finally
            {
                btnSearch.Enabled = true;
                btnStop.Enabled = false;
            }
        }

        private void BtnStop_Click(object sender, EventArgs e)
        {
            cts?.Cancel();
            lblStatus.Text = "正在停止...";
        }

        private void UpdateProgress(int urlCount, ConcurrentDictionary<string, HashSet<string>> phoneUrls)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new Action(() => UpdateProgress(urlCount, phoneUrls)));
                return;
            }

            lblUrlsCount.Text = $"已爬取URL: {urlCount}";
            lblPhonesCount.Text = $"已找到电话: {phoneUrls.Count}";

            // 更新URL列表
            while (lstUrls.Items.Count < urlCount && urlCount > 0 && crawler.ProcessedUrls.Count > lstUrls.Items.Count)
            {
                lstUrls.Items.Add(crawler.ProcessedUrls[lstUrls.Items.Count]);
            }

            // 更新电话号码表格
            gridPhones.Rows.Clear();
            foreach (var pair in phoneUrls)
            {
                gridPhones.Rows.Add(pair.Key, pair.Value.Count);
            }
        }

        private void GridPhones_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex >= 0)
            {
                string phoneNumber = gridPhones.Rows[e.RowIndex].Cells[0].Value.ToString();
                if (crawler.PhoneUrls.TryGetValue(phoneNumber, out HashSet<string> urls))
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendLine($"电话号码 {phoneNumber} 出现在以下URL:");
                    foreach (string url in urls)
                    {
                        sb.AppendLine(url);
                    }
                    MessageBox.Show(sb.ToString(), "URL列表", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }
    }

    public class CrawlerEngine
    {
        private readonly HttpClient _httpClient;
        private ConcurrentDictionary<string, HashSet<string>> _phoneUrls;
        private ConcurrentBag<string> _processedUrls;
        private ConcurrentQueue<string> _urlQueue;
        private HashSet<string> _visitedUrls;
        private int _maxPhones = 100;
        private SemaphoreSlim _semaphore;
        private int _maxDepth = 3;

        public int MaxConcurrency { get; set; } = 5;
        public Action<int, ConcurrentDictionary<string, HashSet<string>>> ProgressCallback { get; set; }

        public List<string> ProcessedUrls => _processedUrls.ToList();
        public ConcurrentDictionary<string, HashSet<string>> PhoneUrls => _phoneUrls;

        public CrawlerEngine()
        {
            _httpClient = new HttpClient();
            _httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
            _phoneUrls = new ConcurrentDictionary<string, HashSet<string>>();
            _processedUrls = new ConcurrentBag<string>();
            _urlQueue = new ConcurrentQueue<string>();
            _visitedUrls = new HashSet<string>();
        }

        public async Task StartCrawlingAsync(string keyword, CancellationToken cancellationToken)
        {
            _phoneUrls.Clear();
            _processedUrls = new ConcurrentBag<string>();
            _urlQueue = new ConcurrentQueue<string>();
            _visitedUrls.Clear();
            _semaphore = new SemaphoreSlim(MaxConcurrency);

            // 初始搜索引擎URL（这里使用百度作为示例）
            string searchUrl = $"https://www.baidu.com/s?wd={Uri.EscapeDataString(keyword)}";

            await ProcessUrlAsync(searchUrl, 0, cancellationToken);

            List<Task> tasks = new List<Task>();

            while (!_urlQueue.IsEmpty || tasks.Count > 0)
            {
                if (cancellationToken.IsCancellationRequested)
                    break;

                if (_phoneUrls.Count >= _maxPhones)
                    break;

                if (_urlQueue.TryDequeue(out string url))
                {
                    await _semaphore.WaitAsync(cancellationToken);

                    try
                    {
                        Task task = ProcessUrlAsync(url, 1, cancellationToken)
                            .ContinueWith(_ => _semaphore.Release());

                        tasks.Add(task);
                    }
                    catch
                    {
                        _semaphore.Release();
                    }
                }

                // 清理已完成的任务
                tasks = tasks.Where(t => !t.IsCompleted).ToList();

                // 如果队列为空但任务仍在进行，等待一下
                if (_urlQueue.IsEmpty && tasks.Count > 0)
                {
                    await Task.Delay(100, cancellationToken);
                }
            }

            // 等待所有任务完成
            await Task.WhenAll(tasks);
        }

        private async Task ProcessUrlAsync(string url, int depth, CancellationToken cancellationToken)
        {
            if (string.IsNullOrEmpty(url) || url.Length > 500)
                return;

            if (_phoneUrls.Count >= _maxPhones)
                return;

            if (depth > _maxDepth)
                return;

            if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
                return;

            // 只处理http和https协议的链接
            if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
                return;

            // 添加域名限制，避免爬取不相关网站
            // 这里可以根据需要修改，例如只爬取edu.cn域名
            // if (!uri.Host.EndsWith("edu.cn"))
            //    return;

            // 跳过已访问的URL
            lock (_visitedUrls)
            {
                if (_visitedUrls.Contains(url))
                    return;
                _visitedUrls.Add(url);
            }

            try
            {
                // 创建一个带有取消令牌的请求
                using (var response = await _httpClient.GetAsync(url, cancellationToken))
                {
                    response.EnsureSuccessStatusCode();
                    string html = await response.Content.ReadAsStringAsync();

                    _processedUrls.Add(url);

                    // 随机延迟以避免被封IP
                    await Task.Delay(new Random().Next(300, 1000), cancellationToken);

                    // 提取电话号码
                    ExtractPhoneNumbers(html, url);

                    // 提取链接并加入队列
                    if (depth < _maxDepth)
                    {
                        ExtractLinks(html, url).ForEach(link => _urlQueue.Enqueue(link));
                    }
                }

                // 更新进度
                ProgressCallback?.Invoke(_processedUrls.Count, _phoneUrls);
            }
            catch (Exception)
            {
                // 忽略处理单个URL时的错误，继续处理其他URL
            }
        }

        private void ExtractPhoneNumbers(string html, string url)
        {
            if (_phoneUrls.Count >= _maxPhones)
                return;

            // 电话号码的正则表达式模式
            // 匹配中国高校常见的电话格式：
            // 1. 手机号：1[3-9]\d{9}
            // 2. 座机号(含区号)：(0\d{2,3}[-])?\d{7,8}
            // 3. 400/800电话：[48]00-\d{3}-\d{4}
            string pattern = @"(1[3-9]\d{9})|((0\d{2,3}[-])?\d{7,8})|([48]00-\d{3}-\d{4})";

            MatchCollection matches = Regex.Matches(html, pattern);

            foreach (Match match in matches)
            {
                string phone = match.Value;

                // 电话号码过滤，避免匹配错误的数字序列
                if (IsLikelyPhoneNumber(phone))
                {
                    _phoneUrls.AddOrUpdate(
                        phone,
                        new HashSet<string> { url },
                        (_, urls) => {
                            urls.Add(url);
                            return urls;
                        }
                    );
                }

                if (_phoneUrls.Count >= _maxPhones)
                    break;
            }
        }

        private bool IsLikelyPhoneNumber(string phone)
        {
            // 过滤明显不是电话号码的数字序列

            // 手机号必须是11位
            if (phone.Length == 11 && phone.StartsWith("1"))
                return true;

            // 座机号通常是区号+7-8位数字
            if (Regex.IsMatch(phone, @"^0\d{2,3}-\d{7,8}$"))
                return true;

            // 不含区号的座机通常是7-8位
            if (Regex.IsMatch(phone, @"^\d{7,8}$"))
                return true;

            // 400/800电话
            if (Regex.IsMatch(phone, @"^[48]00-\d{3}-\d{4}$"))
                return true;

            return false;
        }

        private List<string> ExtractLinks(string html, string baseUrl)
        {
            List<string> links = new List<string>();

            string pattern = @"href=[""']([^""']+)[""']";
            MatchCollection matches = Regex.Matches(html, pattern);

            foreach (Match match in matches)
            {
                string href = match.Groups[1].Value;

                // 跳过空链接、JavaScript和锚点链接
                if (string.IsNullOrEmpty(href) ||
                    href.StartsWith("javascript:") ||
                    href.StartsWith("#"))
                {
                    continue;
                }

                try
                {
                    Uri uri;

                    // 处理相对URL
                    if (!Uri.TryCreate(href, UriKind.Absolute, out uri))
                    {
                        Uri baseUri = new Uri(baseUrl);
                        if (Uri.TryCreate(baseUri, href, out uri))
                        {
                            href = uri.ToString();
                        }
                        else
                        {
                            continue;
                        }
                    }

                    // 只保留http和https协议的链接
                    if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
                    {
                        links.Add(href);
                    }
                }
                catch
                {
                    // 忽略无效的URL
                }
            }

            return links;
        }
    }
}